package com.kehutong.api.util;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.kehutong.common.entity.OAuthClient;
import org.coraframework.cache.Cache;
import org.coraframework.cache.support.FIFOCache;
import org.coraframework.inject.Singleton;
import org.coraframework.oauth2.OAuth2Accessor;
import org.coraframework.oauth2.OAuth2Client;
import org.coraframework.oauth2.OAuth2Exception;
import org.coraframework.oauth2.OAuth2Message;
import org.coraframework.oauth2.OAuth2ProblemException;
import org.coraframework.oauth2.code.ErrorCode;
import org.coraframework.oauth2.code.TokenType;
import org.coraframework.oauth2.provider.OAuth2Servlet;
import org.coraframework.oauth2.provider.OAuth2Validator;
import org.coraframework.orm.Pool;
import org.coraframework.util.Objects;
import org.coraframework.util.secret.MD5;
import org.coraframework.util.time.TimeUtil;

@Singleton
public class OAuth2Provider {

	public static final String ENTERPRISE = "enterprise";
	private static final String WRITEIP = "writeip";
    private static final OAuth2Validator VALIDATOR = new OAuth2Validator();

    private OAuth2Provider() {
    	this.oauth2Map = new ConcurrentHashMap<>();
    	this.codeMap = new FIFOCache<>("code-cache", 10*1024*1024, 5 * TimeUtil.MILLIS_PER_MINUTE);
    	this.accessTokenMap = new FIFOCache<>("access-cache", 20*1024*1024, 2 * TimeUtil.MILLIS_PER_HOUR);
    }

    private final ConcurrentMap<String, OAuth2Accessor> oauth2Map;
    private final Cache<String, OAuth2Accessor> codeMap;
    private final Cache<String, OAuth2Accessor> accessTokenMap;

    public OAuth2Client getOAuth2ClientByAppId(String appId) throws OAuth2ProblemException {
        final OAuth2Accessor client = this.getAccesor(appId);

        if(client == null) {
            throw new OAuth2ProblemException(ErrorCode.INVALID_CLIENT);
        }

        return client.getClient();
    }

    public void generateCode(OAuth2Accessor accessor) throws OAuth2Exception {
        // generate authorization code
        String appId = accessor.getClient().getAppId();

        // for now use md5 of client_id + current time as token
        String code_data = appId + System.nanoTime();
        String code =  MD5.MD5Encode(code_data);
        accessor.setCode(code);

        codeMap.put(code, accessor);

    }

    public OAuth2Accessor getAccessorByCode(String code) throws OAuth2ProblemException {
        if(code == null){
        	throw new OAuth2ProblemException(ErrorCode.INVALID_REQUEST);
        }

        final OAuth2Accessor accessor = codeMap.get(code);
        if(accessor == null){
        	throw new OAuth2ProblemException(ErrorCode.INVALID_REQUEST);
        }

        return accessor;
    }

    public OAuth2Accessor getAccessorByAppId(String appId) throws OAuth2ProblemException {
        final OAuth2Accessor accessor = this.getAccesor(appId);

        if(accessor == null) {
            throw new OAuth2ProblemException(ErrorCode.INVALID_CLIENT);
        }

        return accessor;
    }


    /**
     * Get the accessor for the given Refresh Token.
     */
    public OAuth2Accessor getAccessorByAccessToken(String accessToken)
    throws OAuth2ProblemException {
    	if(accessToken == null){
    		throw new OAuth2ProblemException(ErrorCode.INVALID_REQUEST);
        }

    	final OAuth2Accessor accessor = accessTokenMap.get(accessToken);
        if(accessor == null){
        	throw new OAuth2ProblemException(ErrorCode.INVALID_TOKEN);
        }

        return accessor;
    }


    /**
     * Generate an access token and fresh token.
     *
     * @throws OAuth2Exception
     */
    public void generateAccessToken(OAuth2Accessor accessor)
            throws OAuth2Exception {
        final String client_id = accessor.getClient().getAppId();

        // for now use md5 of client_id + current time as token
        String access_token_data = client_id + System.nanoTime();
        String accessToken = MD5.MD5Encode(access_token_data);


        accessor.setAccessToken(accessToken);
        accessor.setTokenType(TokenType.BEARER);
        accessor.setExpiresTime(7200);

        // update token in local cache
        accessTokenMap.put(accessToken, accessor);
    }


    /**
     * Set mark the accessor as authorized
     */
    public static void markAsAuthorized(OAuth2Accessor accessor, String userId) {
        accessor.setProperty("user", userId);
        accessor.setProperty("authorized", Boolean.TRUE);
    }

    /**
     * Set mark the accessor as authorized
     */
    public static void markAsAuthorized(OAuth2Accessor accessor, String uuid,String userId, String corpId,String extContent) {
        accessor.setProperty("user", userId);
        accessor.setProperty("uuid", uuid);
        accessor.setProperty("authorized", Boolean.TRUE);
        accessor.setProperty("corpId", corpId);
        if(Objects.nonEmpty(extContent)){
            accessor.setProperty("extContent",extContent);
        }
    }
    /*
     * handle exceptions
     */
    public static void handleException(Exception e, HttpServletRequest request,
            HttpServletResponse response, boolean sendBodyInJson, boolean withAuthHeader)
            throws IOException, ServletException {
    	String realm = null;

    	OAuth2Servlet.handleException(request, response, e, realm, sendBodyInJson, withAuthHeader);
    }

    public void validateRequestMessageForAccessToken(OAuth2Message message, OAuth2Accessor accessor)
	throws OAuth2Exception, IOException, URISyntaxException {
    	VALIDATOR.validateRequestMessageForAccessToken(message, accessor);
    }


    private OAuth2Accessor getAccesor(String appId) {
    	OAuth2Accessor accessor = oauth2Map.get(appId);
    	if (Objects.nonNull(accessor)) {
    		return accessor;
    	}

    	OAuthClient client = Pool.get(OAuthClient.class, appId);
    	if (Objects.isNull(client)) {
    		return null;
    	}

    	OAuth2Client aclient = new OAuth2Client(client.getRedirectUri(), client.getAppid(), client.getSecret());
    	accessor = new OAuth2Accessor(aclient);
    	aclient.setProperty(ENTERPRISE, client.getCompanyNo());
    	accessor.setProperty(ENTERPRISE, client.getCompanyNo());

    	accessor.setProperty("companyNo", client.getCompanyNo());
    	accessor.setProperty("companyName", client.getCompanyName());

    	aclient.setProperty(WRITEIP, client.getWhiteIp());
    	accessor.setProperty(WRITEIP, client.getWhiteIp());
    	oauth2Map.putIfAbsent(appId, accessor);

    	return accessor;
    }

    public static String getRandom(){
        Random random=new Random();
        int rannum= (int)(random.nextDouble()*(99999-10000 + 1))+ 10000;
        // System.out.println(rannum);
        String rand=String.valueOf(rannum);
        return rand;
    }
}
